#include <math.h>
#include <FL/fl_ask.H>
#include <FL/Fl_File_Chooser.H>
#include "MyGlWindow.h"
#include "Sound.h"
#include <stdio.h>
#include "global.h"

Sound::Sound()
{
    totalSamples = 0;
    //tempo = 150;
    tempo = gIni.GetLongValue("general","Tempo", 150);
    startMarker = 0;
    endMarker = gIni.GetLongValue("general","EndMarker", 16);
    SAMPLE_RATE = gIni.GetLongValue("general","SampleRate", 44100);
    FRAMES_PER_BUFFER = gIni.GetLongValue("general","FramesPerBuffer", 64);
    DEVICE = gIni.GetLongValue("general","DeviceNumber", -1);
    CHANNELS = gIni.GetLongValue("general","Channels", 2);
    CHANNEL_OFFSET = gIni.GetLongValue("general","ChannelOffset", 0);


    GenericPluginRegistry& registry(GenericPluginRegistry::get());
    for(GenericPluginRegistry::iterator it = registry.begin(); it != registry.end(); ++it)
    {
        genericPlugin_creator func = *it;
        GenericPlugin* _ptr = func();
        _ptr->preInitMaster();
        plugins.push_back(_ptr);
    }
}


// that could mess up the system like calling malloc() or free().
static int portAudioCallback( const void *inputBuffer, void *outputBuffer,
                            unsigned long framesPerBuffer,
                            const PaStreamCallbackTimeInfo* timeInfo,
                            PaStreamCallbackFlags statusFlags,
                            void *data )
{
    ((Sound *)data)->processBuffer( (float *)outputBuffer, framesPerBuffer);
    ((Sound *)data)->totalSamples += framesPerBuffer;
    return 0;
}

static void StreamFinished( void* userData )    //This routine is called by portaudio when playback is done.
{}




int Sound::openAudio()
{
    PaStreamParameters outputParameters;
    PaError err;

    err = Pa_Initialize();
    if( err != paNoError ) goto error;

    if (DEVICE == -1)
        DEVICE = Pa_GetDefaultOutputDevice();
    outputParameters.device = DEVICE; /* default output device  TRY 1*/


    outputParameters.channelCount = CHANNELS;
    outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */
    outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
    outputParameters.hostApiSpecificStreamInfo = NULL;
   //fl_alert("o");
    err = Pa_OpenStream(
              &stream,
              NULL, /* no input */
              &outputParameters,
              SAMPLE_RATE,
              FRAMES_PER_BUFFER,
              0,
              portAudioCallback,
              (void *)this );
 //  fl_alert("d");
    if( err != paNoError ) goto error;

    err = Pa_SetStreamFinishedCallback( stream, &StreamFinished );
    if( err != paNoError ) goto error;

    err = Pa_StartStream( stream );
    if( err != paNoError ) goto error;

    error:
    Pa_Terminate();
  //  fl_alert(Pa_GetErrorText( err ));
    //fprintf( stderr, "An error occured while using the portaudio stream\n" );
    //fprintf( stderr, "Error number: %d\n", err );
    //fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
    return err;
}

int Sound::closeAudio()
{
    PaError err = Pa_StopStream( stream );
    if( err == paNoError )
        err = Pa_CloseStream( stream );

    Pa_Terminate();
    return err;
}


void Sound::processBuffer( float *outputBuffer, unsigned long framesPerBuffer)
{
    MyGlWindow * pGlWindow = (MyGlWindow*)vpGlWindow;

    long startTick, startOffset, endTick, endOffset;
    totalChannels = 0;

    samplesToTicks( totalSamples, &startTick,  &startOffset);
    samplesToTicks( totalSamples + FRAMES_PER_BUFFER, &endTick, &endOffset);

    if (endTick >= endMarker)
    {
        totalSamples = TicksToSamples(startMarker) + endOffset ;
        endTick = startMarker;
    }


    if (startTick<startMarker || endTick>endMarker+1)
    {
        startTick = startMarker;
        totalSamples = TicksToSamples(startMarker);
        samplesToTicks( totalSamples, &startTick,  &startOffset);
        samplesToTicks( totalSamples + FRAMES_PER_BUFFER, &endTick, &endOffset);
    }

    //clear the buffer
    float *outputBufferClearing = outputBuffer;
    for ( unsigned long i=0; i<framesPerBuffer; i++ )
    {
        *outputBufferClearing++ = 0;
        *outputBufferClearing++ = 0;
    }

    for (int i = 0; i<plugins.size(); i++)
    {
        getEvents(i, startTick, endTick, endOffset);

        plugins[i]->preProcessBuffer( outputBuffer, framesPerBuffer, totalSamples);
        totalChannels++;
        for (int iSlave = 0; iSlave<plugins[i]->slaves.size(); iSlave++)
        {
            plugins[i]->slaves[iSlave]->preProcessBuffer( outputBuffer, framesPerBuffer, totalSamples);
            totalChannels++;
            if ( plugins[i]->slaves[iSlave]->bIsFree ) plugins[i]->slaves.erase( plugins[i]->slaves.begin() + iSlave );
        }
    }

    if (startTick != endTick) //update time grid
    {
        pGlWindow->setPlayCursor ( startTick );
        MyGlWindow * pGlWindow = (MyGlWindow*)vpGlWindow;
        sprintf(pGlWindow->messageString, "channels = %d", totalChannels);
    }
}


unsigned long Sound::TicksToSamples(long ticks)
{
    int samplesPerTick = (SAMPLE_RATE  * 60) / (tempo * 4);
    return ticks * samplesPerTick;
}

void Sound::samplesToTime(unsigned long sampleLocation, long *bars, long *ticks, long *sampleOffset)
{
    int tickPeriod = (SAMPLE_RATE  * 60) / (tempo * 4);
    int barPeriod = tickPeriod * 16;

    *bars = sampleLocation / barPeriod;
    *ticks = (sampleLocation / tickPeriod) % 16;

    *sampleOffset = sampleLocation % tickPeriod;
}



void Sound::samplesToTicks(unsigned long sampleLocation,  long *ticks, long *sampleOffset)
{
    int tickPeriod = (SAMPLE_RATE  * 60) / (tempo * 4);

    *ticks = sampleLocation / tickPeriod;

    *sampleOffset = sampleLocation % tickPeriod;
}

unsigned long Sound::tickFractionToSamples( double x )
{
    int tickPeriod = (SAMPLE_RATE  * 60) / (tempo * 4);
    //tick fraction to samples
    //so zero would be zero and 1 would be tickPeriod
    return (unsigned long)(x * tickPeriod);
}


void Sound::getEvents(int pluginIdIn, long startTick, long endTick, long endOffset)
{
    if (startTick==endTick)
        return; //we are only interested in tick boundaries

    startTick = endTick;
    endTick++;

    if (endTick>=endMarker)
        endTick = endMarker;

    double x;


    //maybe go through all the notes and find the ones that have an x between startTick and endTick;
    for (int i=0; i<notes.size(); i++)
    {

        //first look for a note on
        x = notes[i].x;
        if (  ( x >= startTick && x < endTick) && (pluginIdIn == notes[i].pluginId)  )
        {
           //fl_alert("a note! = %d", notes[i].x);
            pluginEvent  * event = new pluginEvent;
            event->sampleOffset = FRAMES_PER_BUFFER - endOffset - 1 + tickFractionToSamples( x - (double)startTick );//beatOffset;
            event->eventType = EVT_NOTE_ON;
            eventData *ed = new eventData;
            ed->gain = notes[i].y;
            ed->speed = notes[i].w;
            event->data = ed;

            int pluginId = notes[i].pluginId;


            //see if the plugin master is free or if it is just monophonic
            if (plugins[pluginId]->bIsFree || !plugins[pluginId]->bIsPolyphonic)
            {
                plugins[pluginId]->addEvent(event);
                plugins[pluginId]->slaveId = notes[i].id;
            }
            else //see if a slave is free
            {
                GenericPlugin * aFreeSlave = plugins[pluginId]->getAFreeSlave(notes[i].id);
                //fl_alert("slaves = %d", plugins[pluginId]->slaves.size() );
                if (aFreeSlave) //should always get a slave here, but just in case
                {
                    aFreeSlave->addEvent(event);
                    //notes[i].slaveId = aFreeSlave->slaveId;
                }
            }
        }
        //next look for a note off
        x = notes[i].x+notes[i].w;
        if ( x >= startTick && x < endTick)
        {
           //fl_alert("a note off!sid = %d", notes[i].slaveId);
            pluginEvent  * event = new pluginEvent;
            event->sampleOffset = FRAMES_PER_BUFFER - endOffset - 1 + tickFractionToSamples( x - (double)startTick );//beatOffset;
            event->eventType = EVT_NOTE_OFF;
            event->data = NULL;

            int pluginId = notes[i].pluginId;

            //see if this note belongs to the master OR is not polyphonic
            if (!plugins[pluginId]->bIsPolyphonic || plugins[pluginId]->slaveId == notes[i].id )
            {//then add the event to the master
                plugins[pluginId]->addEvent(event);
               // fl_alert("master note off");
            }
            else
                //find the right slave
                for (int iS = 0; iS<plugins[pluginId]->slaves.size(); iS++)
                    if (plugins[pluginId]->slaves[iS]->slaveId == notes[i].id)
                    {//then found it
                        plugins[pluginId]->slaves[iS]->addEvent(event);
                        //sprintf(pGlWindow->messageString, "notes = %d", plugins[pluginId]->slaves[iS]->currentEvents.size());
                        break;
                       // fl_alert("slave note off");
                    }
        }
    }
}


void Sound::save()
{
    MyGlWindow * pGlWindow = (MyGlWindow*)vpGlWindow;
    // The FL_File_Chooser is a subclass of Fl_Group so it can be used like the Fl_Group.
    Fl_File_Chooser* fc = new Fl_File_Chooser(".","(*.txt)",Fl_File_Chooser::CREATE,"Note File:");
    // Now call the file chooser ...
    fc->show();
    // ... them make FL wait until the file selection is done
    while(fc->shown())
          Fl::wait();

    FILE * pFile;

    pFile = fopen (fc->value(),"w");


    //write how many notes
    fprintf (pFile, "%d\n",notes.size());
    fprintf (pFile, "%d\n",pGlWindow->glObjectIdIndex);

    for (int i=0; i<notes.size(); i++)
    {
        NoteBox * pNote = &notes[i];
        fprintf (pFile, "%lf ", pNote->x);
        fprintf (pFile, "%d ", pNote->y);
        fprintf (pFile, "%lf ", pNote->w);
        fprintf (pFile, "%lf ", pNote->h);
        fprintf (pFile, "%d ", pNote->id);
        fprintf (pFile, "%d ", pNote->idSizer);
        fprintf (pFile, "%d\n", pNote->pluginId);
    }
    fclose (pFile);
}

void Sound::load()
{
    MyGlWindow * pGlWindow = (MyGlWindow*)vpGlWindow;
        // The FL_File_Chooser is a subclass of Fl_Group so it can be used like the Fl_Group.
    Fl_File_Chooser* fc = new Fl_File_Chooser(".","(*.txt)",Fl_File_Chooser::SINGLE,"File:");
    // Now call the file chooser ...
    fc->show();
    // ... them make FL wait until the file selection is done
    while(fc->shown())
          Fl::wait();

    FILE * pFile;

    pFile = fopen (fc->value(),"r");

    int numOfNotes;


    fscanf (pFile, "%d\n", &numOfNotes);
    fscanf (pFile, "%d\n",&(pGlWindow->glObjectIdIndex));

    for (int i=0; i<numOfNotes; i++)
    {
        NoteBox * pNote = new NoteBox();
        fscanf (pFile, "%lf ", &pNote->x);
        fscanf (pFile, "%d ", &pNote->y);
        fscanf (pFile, "%lf ", &pNote->w);
        fscanf (pFile, "%lf ", &pNote->h);
        fscanf (pFile, "%d ", &pNote->id);
        fscanf (pFile, "%d ", &pNote->idSizer);
        fscanf (pFile, "%d\n", &pNote->pluginId);
        notes.push_back(*pNote);
    }
    fclose (pFile);
}
